package prim;

import math.Vecmath;
import photon.Photon;
import ray.Ray;
import render.ColorMap;

public class PRect extends Primitive
{
    
    private static final int LUXELS_PER_WORLD_UNIT = 512;
    
    public static final int AXIS_X = 0;
    public static final int AXIS_Y = 1;
    public static final int AXIS_Z = 2;
    
    public static final int DIR_POS = 1;
    public static final int DIR_NEG = -1;
    
    
    private int normalAxis;
    public void setNormalAxis(final int axisNormal) { this.normalAxis = axisNormal; }
    
    private int normalDir;
    public void setNormalDirection(final int dirNormal) { this.normalDir = dirNormal; }
    
    public double[] getNormal()
    {
        double[] norm;
        if (this.normalAxis == PRect.AXIS_X)
        {
            norm = new double[] { 1.0, 0.0, 0.0 };
            return norm;
        }
        else if (this.normalAxis == PRect.AXIS_Y)
        {
            norm = new double[] { 0.0, 1.0, 0.0 };
        }
        else
        {
            norm = new double[] { 0.0, 0.0, 1.0 };
        }
        Vecmath.scale(norm, this.normalDir);
        return norm;
    }
    
    private double[] dim;
    public void setDimension(double[] dimension)
    {
        this.dim = dimension;
        this.autoCalcColorMap();
    }
    
    private ColorMap colorMap;
    public ColorMap getColorMap() { return this.colorMap; }
    //<editor-fold defaultstate="collapsed" desc="autoCalcColorMap">
    private void autoCalcColorMap()
    {
        this.colorMap = new ColorMap(
            (int)(this.dim[0] * PRect.LUXELS_PER_WORLD_UNIT + 0.5),
            (int)(this.dim[1] * PRect.LUXELS_PER_WORLD_UNIT + 0.5)
        );
    }
    //</editor-fold>
    
    public PRect()
    {
        this.normalAxis = -1;
        this.normalDir = -1;
        this.dim = new double[2];
    }
    
    
    public double[] getColor(double[] pos)
    {
        int[] luxelInds = this.getLuxelIndicesAt(pos);
        double[] luxelColor = this.colorMap.getColor(luxelInds[0], luxelInds[1]);
        return luxelColor;
    }
    
    public double[] getNormal(double[] pos)
    {
        return this.getNormal();
    }
    
    public double[] getPosInside(
            double t0, 
            double t1, 
            double t2)
    {
        if (this.normalAxis == PRect.AXIS_X)
        {
            double[] pos = new double[3];
            pos[0] = this.origin[0] + 0.5 * t0 * this.dim[0];
            pos[1] = this.origin[1] + 0.5 * t1 * this.dim[1];
            pos[2] = this.origin[2];
            return pos;
        }
        else if (this.normalAxis == PRect.AXIS_Y)
        {
            double[] pos = new double[3];
            pos[0] = this.origin[0] + 0.5 * t0 * this.dim[0];
            pos[1] = this.origin[1];
            pos[2] = this.origin[2] + 0.5 * t2 * this.dim[1];
            return pos;
        }
        else
        {
            double[] pos = new double[3];
            pos[0] = this.origin[0];
            pos[1] = this.origin[1] + 0.5 * t1 * this.dim[0];
            pos[2] = this.origin[2] + 0.5 * t2 * this.dim[1];
            return pos;
        }
    }
    
    public double getIntersectT(Ray ray)
    {
        double[] normal = this.getNormal();
        double denom = Vecmath.dotProd(normal, ray.getDirection());
        if (denom == 0.0 || denom == -0.0) // No intersection
        {
            return Double.MAX_VALUE;
        }
        else // Intersects
        {
            double intersectT = (
                -1.0 * (
                    Vecmath.dotProd(normal, ray.getOrigin())
                    - Vecmath.dotProd(this.origin, normal)
                    )
                / denom
            );
            double[] intersectPt = ray.getPointAt(intersectT);
            if (this.normalAxis == PRect.AXIS_X)
            {
                boolean withinY = (Math.abs(intersectPt[1] - this.origin[1]) <= 0.5 * this.dim[0]);
                boolean withinZ = (Math.abs(intersectPt[2] - this.origin[2]) <= 0.5 * this.dim[1]);
                if (withinY && withinZ)
                {
                    return intersectT;
                }
            }
            else if (this.normalAxis == PRect.AXIS_Y)
            {
                boolean withinX = (Math.abs(intersectPt[0] - this.origin[0]) <= 0.5 * this.dim[0]);
                boolean withinZ = (Math.abs(intersectPt[2] - this.origin[2]) <= 0.5 * this.dim[1]);
                if (withinX && withinZ)
                {
                    return intersectT;
                }
            }
            else
            {
                boolean withinX = (Math.abs(intersectPt[0] - this.origin[0]) <= 0.5 * this.dim[0]);
                boolean withinY = (Math.abs(intersectPt[1] - this.origin[1]) <= 0.5 * this.dim[1]);
                if (withinX && withinY)
                {
                    return intersectT;
                }
            }
            return Double.MAX_VALUE;
        }
    }
    
    public void absorbPhoton(
            Photon photon, 
            double[] intersectPoint)
    {
        int[] luxelInds = this.getLuxelIndicesAt(intersectPoint);
        double[] colorPhoton = photon.getColor();
        double[] colorPrim = this.color;
        double[] colorOutput = Vecmath.multiply(colorPhoton, colorPrim);
        
        // Add on the photon's contribution
        this.colorMap.addColor(luxelInds[0], luxelInds[1], colorOutput);
        
        Vecmath.scale(colorOutput, this.reflection);
        photon.setColor(colorOutput);
    }
    
    private int[] getLuxelIndicesAt(double[] intersectPoint)
    {
        int[] luxInds = new int[2];
        
        double[] ipLocal = Vecmath.subtract(intersectPoint, this.origin);
        
        if (this.normalAxis == PRect.AXIS_X)
        {
            double tZ = 0.5 * (ipLocal[2] * (1.0 / this.dim[0]) + 1.0);
            double tY = 0.5 * (ipLocal[1] * (1.0 / this.dim[1]) + 1.0);
            luxInds[0] = (int)(tZ * this.colorMap.getNumPixelsX() + 0.5);
            luxInds[1] = (int)(tY * this.colorMap.getNumPixelsY() + 0.5);
        }
        else if (this.normalAxis == PRect.AXIS_Y)
        {
            double tX = 0.5 * (ipLocal[0] * (1.0 / this.dim[0]) + 1.0);
            double tZ = 0.5 * (ipLocal[2] * (1.0 / this.dim[1]) + 1.0);
            luxInds[0] = (int)(tX * this.colorMap.getNumPixelsX() + 0.5);
            luxInds[1] = (int)(tZ * this.colorMap.getNumPixelsY() + 0.5);
        }
        else
        {
            double tX = 0.5 * (ipLocal[0] * (1.0 / this.dim[0]) + 1.0);
            double tY = 0.5 * (ipLocal[1] * (1.0 / this.dim[1]) + 1.0);
            luxInds[0] = (int)(tX * this.colorMap.getNumPixelsX() + 0.5);
            luxInds[1] = (int)(tY * this.colorMap.getNumPixelsY() + 0.5);
        }
        
        return luxInds;
    }
    
    public void destroy()
    {
        if (this.colorMap != null)
        {
            this.colorMap.clearData();
            this.colorMap = null;
        }
    }
    
}
